﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MessageServer.channels;
using System.Collections;
using MessageServer.pool;
using MessageServer.Handlers.hinternal;
using MessageServer.config;
using MessageServer.persistence;

namespace MessageServer.Handlers
{
    /**
     * A registry to store the message processors for different application messages
     * defined. This is a singleton class. Unless registered here, no processing will
     * be done for the messages received.
     * */
    public class MessageListenerRegistry
    {
        //singleton instance of the registry
        static MessageListenerRegistry REGISTRY = new MessageListenerRegistry();

        //accessor for the singleton instance.
        public static MessageListenerRegistry lregistry { get { return REGISTRY; } }

        //a hashtable that stores the processors to be used.
        Hashtable _msgProcessors = new Hashtable();

        /**
         * Register can be called more than once of the same message. This will create
         * a list of the listeners registered and call them sequentially in the order
         * they were registered. This registers a listener for the given message type.
         * */
        public void register(Type msg, Type listener)
        {
            List<Type> listeners;
            if (_msgProcessors.ContainsKey(msg))
                listeners = (List<Type>)_msgProcessors[msg];
            else
                listeners = new List<Type>();

            listeners.Add(listener);
            _msgProcessors.Add(msg, listeners);
        }

        /**
         * Returns the listeners registered for the given message type.
         * */
        public List<Type> listeners(Type t)
        {
            return (List<Type>)_msgProcessors[t];
        }
    }

    /**
     * Listeners that can handle messages have to implement the below interface. This
     * provides lifecycle functions that will be called during different lifecycles of
     * the given listener. Any initialization can be done as a part of the constructor
     * any uninitialization can be done as a part of the dispose. Activated is called
     * when this listener is going to be used to process a message. The passivated is called
     * when this listener has completed and is going to be released back to the pool.
     * */
    public interface MessageListener : IDisposable
    {
        //Lifecycle functions
        /**
         * Called when this listener is activated to process a given event
         * */
        void activated();

        /**
         * Called when this listener is passivated after processing of a given event
         * */
        void passivated();

        /**
         * This function should implement the functionality for processing the 
         * given event for the message msg.
         * */
        void onMessage(ChannelEvent evt, Object msg);
    }

    /**
     * The handler class that will use the listener registry to call the appropriate
     * listener for the given message. The MessageFormatHandler will format the messages
     * and pass it to the processorhandler. This will look up the correct listener for the
     * given message and retrieve an instance to use to handle the message from the pool
     * and call the onMessage function. This is just the Upstream handler.
     * */
    class MessageProcessorHandler : UpStreamHandler
    {
        MessageServerConfig _config;
        TuningParams _parms;

        //Constructor for the handler.
        public MessageProcessorHandler(MessageServerConfig conf, TuningParams parms)
        {
            _config = conf;
            _parms = parms;
        }

        public void Dispose()
        {
        }

        //the name by which this handler will be recognized
        public String handle { get { return "MessageProcessor"; } }

        /**
         * This function uses the ListenerRegistry to find the listener for the given message
         * It uses the ListenerContainer to retrieve the pool for the given listener and tries
         * to retrieve an object from the free pool to process this message. If not present, it
         * tries it thrice before throwing an exception.
         * */
        public HandlerState handleUpStream(HandlerContext ctx, ChannelEvent evt)
        {
            if (evt is UpstreamMessageEvent)
            {
                UpstreamMessageEvent msg = evt as UpstreamMessageEvent;
                List<Object> msgs = (List<Object>)msg.getMessage();
                //process each message
                PooledPersister persister = null;
                if (_config.persistentMessages)
                    persister = PooledPersister.getInstance(_config, _parms);
                foreach (Object omsg in msgs)
                {
                    List<Type> lst = MessageListenerRegistry.lregistry.listeners(omsg.GetType());
                    if (lst != null)
                    {
                        if (_config.persistentMessages) persister.markProcessing(omsg);
                        ListenerContainer cont = ListenerContainer.container;
                        foreach (Type l in lst)
                        {
                            //if this is the first time, create the pool
                            //have as many object as socket workers
                            if (!cont.isManaging(l))
                            {
                                cont.addPool(l, _parms.WaitForThread,
                                    _parms.MinPipelineThreads, _parms.MaxPipelineThreads);
                            }

                            //get the pool for this object
                            ListenerPool p = cont.getPool(l);
                            int trial = 3; //for not try 3 times, have to check otherwise.
                            bool executed = false;
                            while ((trial > 0) && (!executed))
                            {
                                PoolObject o = p.get(); //get an object
                                if (o != null)
                                {
                                    //TODO: Integration with the transaction manager.
                                    //start the transaction here, so that it is available
                                    //for the processors
                                    ListenerObject lo = o.poolobject as ListenerObject;
                                    lo.listener.onMessage(evt, omsg);
                                    p.release(o);
                                    executed = true;
                                    //TODO: end the transaction here.
                                }
                                else
                                    ChannelLogger.LOG.LogInfo("MessageProcessor:handleUpStream:Did not get a listener object");
                                trial--;
                            }
                        }
                        if (_config.persistentMessages) persister.markProcessed(omsg);
                    }
                    else
                    {
                        //no listener to process
                        if (_config.persistentMessages) persister.markProcessed(omsg);
                        ChannelLogger.LOG.LogInfo("MessageProcessorHandler:handleUpStream:No Listeners registered");
                    }
                }
            }

            ctx.streamUp(evt);
            return HandlerState.gonext;
        }
    }
}
